﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace OptiGear1
{
    class Optimiser
    {
        private int targetHit;
        private double lostHitWeight;
        private double spellpower_weight;
        private double stamina_weight;
        private double haste_weight;
        private double spirit_weight;
        private double crit_weight;
        private double intel_weight;

        private double t7_2pc;
        private double t8_2pc;
        private double t9_2pc;
        private double t10_2pc;
        private double t7_4pc;
        private double t8_4pc;
        private double t9_4pc;
        private double t10_4pc;

        private List<Gearset> gearsets;
        private List<WowItem> gear;
        private List<WowItem> lockedItems;
        private List<WowGem> gems;
        private int[] lockedBodyParts;

        private double progress;

        public Optimiser(int targetHit, double spellpower_weight, double haste_weight,
                    double spirit_weight, double crit_weight, double intel_weight, double stamina_weight,
                    double lostHitWeight, double t7_2pc, double t7_4pc, double t8_2pc,
                    double t8_4pc, double t9_2pc, double t9_4pc, double t10_2pc, double t10_4pc)
        {
            this.targetHit = targetHit;
            this.lostHitWeight = lostHitWeight;
            this.spellpower_weight = spellpower_weight;
            this.stamina_weight = stamina_weight;
            this.haste_weight = haste_weight;
            this.spirit_weight = spirit_weight;
            this.crit_weight = crit_weight;
            this.intel_weight = intel_weight;

            this.t7_2pc = t7_2pc;
            this.t8_2pc = t8_2pc;
            this.t9_2pc = t9_2pc;
            this.t10_2pc = t10_2pc;
            this.t7_4pc = t7_4pc;
            this.t8_4pc = t8_4pc;
            this.t9_4pc = t9_4pc;
            this.t10_4pc = t10_4pc;

            gearsets = new List<Gearset>();            
            progress = 0.0;
        }

        public List<Gearset> GearsetList
        {
            get { return gearsets; }
        }

        public double Progress
        {
            get { return progress; }
        }

        
        public void Optimise(List<WowItem> gear, List<WowGem> gems, int[] lockedBodyParts, List<WowItem> lockedItems)
        {
            this.gear = gear;
            this.gems = gems;
            this.lockedBodyParts = lockedBodyParts;
            this.lockedItems = lockedItems;

            //this method should start a new thread and return.
            Thread thread1 = new Thread(new ThreadStart(CalculateOptimalGearset));
            thread1.IsBackground = true;
            thread1.Start();
        }

        private void CalculateOptimalGearset()
        {
            //all gear loaded. Now each needs a weight

            //first thing to do is calculate all gem weights
            //and remove useless ones.
            //then apply gems to gear
            for (int i = 0; i < gems.Count; i++)
            {
                gems[i].weight = 0.0;
                gems[i].weight += gems[i].spellpower * spellpower_weight;
                gems[i].weight += gems[i].haste * haste_weight;
                gems[i].weight += gems[i].spirit * spirit_weight;
                gems[i].weight += gems[i].crit * crit_weight;
                gems[i].weight += gems[i].intel * intel_weight;
                gems[i].weight += gems[i].stamina * stamina_weight;
                if (gems[i].Name.Contains("Stormjewel") || gems[i].Name.Contains("Dragon"))
                {
                    gems.Remove(gems[i]);
                    i--;
                }
            }

            //and remove inferior gear for each bodypart...
            List<WowGem> bluegems = gems.FindAll(delegate(WowGem w) { return w.blue == 1; });
            List<WowGem> redgems = gems.FindAll(delegate(WowGem w) { return w.red == 1; });
            List<WowGem> yellowgems = gems.FindAll(delegate(WowGem w) { return w.yellow == 1; });

            bluegems.Sort(new WowGemComparer());
            redgems.Sort(new WowGemComparer());
            yellowgems.Sort(new WowGemComparer());
            List<WowGem> usefulGems = new List<WowGem>();
            for (int i = 0; i < bluegems.Count; i++)
            {

                //if 1st item, or item has different hit (less) than the one before
                //and more weight!
                if (i == 0 || (bluegems[i].hit < bluegems[i - 1].hit && bluegems[i].weight > bluegems[i - 1].weight))
                {
                    //take all gems with enough weight into 'passed' set
                    usefulGems.AddRange(bluegems.FindAll(delegate(WowGem w) { return w.hit == bluegems[i].hit && w.weight > (bluegems[i].weight - 0.01); }));
                }
            }
            for (int i = 0; i < redgems.Count; i++)
            {
                //if 1st item, or item has different hit (less) than the one before
                //and more weight!
                if (i == 0 || (redgems[i].hit < redgems[i - 1].hit && redgems[i].weight > redgems[i - 1].weight))
                {
                    //take all gems with enough weight into 'passed' set
                    usefulGems.AddRange(redgems.FindAll(delegate(WowGem w) { return w.hit == redgems[i].hit && w.weight > (redgems[i].weight - 0.01); }));
                }
            }
            for (int i = 0; i < yellowgems.Count; i++)
            {
                //if 1st item, or item has different hit (less) than the one before
                //and more weight!
                if (i == 0 || (yellowgems[i].hit < yellowgems[i - 1].hit && yellowgems[i].weight > yellowgems[i - 1].weight))
                {
                    //take all gems with enough weight into 'passed' set
                    usefulGems.AddRange(yellowgems.FindAll(delegate(WowGem w) { return w.hit == yellowgems[i].hit && w.weight > (yellowgems[i].weight - 0.01); }));
                }
            }
            gems = usefulGems;
            gems.Sort(new WowGemComparer());

            //remove duplicates?
            for (int i = 1; i < gems.Count; i++)
            {
                if (gems[i].name == gems[i - 1].name)
                {
                    gems.RemoveAt(i);
                }

            }

            //now... combine all gear with gems!

            int counter = 0;
            for (int i = 0; i < gear.Count; i++)
            {
                counter++;
                if (counter % 100 == 0)
                {
                    counter = 0;
                }

                if (gear[i].wowheadid == 49890 || gear[i].name.Contains("Deathfrost"))
                {
                }

                //if it has empty gem slots...
                if (gear[i].redsockets + gear[i].bluesockets + gear[i].yellowsockets + gear[i].genericsockets > gear[i].Gems.Count)
                {
                    //if (gear[i].redsockets > 0)
                    //{
                    for (int j = 0; j < gems.Count; j++)
                    {
                        WowItem temp = (WowItem)(gear[i].Clone());
                        temp.AddGem(gems[j]);
                        gear.Add(temp);
                    }
                    gear.RemoveAt(i);
                    i = -1; //reset after removing an item, to be safe.
                    //}
                }
            }

            for (int i = 0; i < gear.Count; i++)
            {
                bool bonus_intact = true;
                int redsockets = gear[i].redsockets;
                int bluesockets = gear[i].bluesockets;
                int yellowsockets = gear[i].yellowsockets;
                for (int j = 0; j < gear[i].Gems.Count; j++)
                {
                    if (redsockets > 0)
                    {
                        if (gear[i].Gems[j].red != 1)
                            bonus_intact = false;
                        else
                            redsockets--;
                    }
                    else if (bluesockets > 0)
                    {
                        if (gear[i].Gems[j].blue != 1)
                            bonus_intact = false;
                        else
                            bluesockets--;
                    }
                    else if (yellowsockets > 0)
                    {
                        if (gear[i].Gems[j].yellow != 1)
                            bonus_intact = false;
                        else
                            yellowsockets--;
                    }

                    gear[i].hit += gear[i].Gems[j].hit;
                    gear[i].spellpower += gear[i].Gems[j].spellpower;
                    gear[i].stamina += gear[i].Gems[j].stamina;
                    gear[i].haste += gear[i].Gems[j].haste;
                    gear[i].spirit += gear[i].Gems[j].spirit;
                    gear[i].crit += gear[i].Gems[j].crit;
                    gear[i].intel += gear[i].Gems[j].intel;
                    //gear[i].name += " + " + gear[i].Gems[j].name;
                }
                gear[i].weight = 0.0;
                gear[i].weight += gear[i].spellpower * spellpower_weight;
                gear[i].weight += gear[i].stamina * stamina_weight;
                gear[i].weight += gear[i].haste * haste_weight;
                gear[i].weight += gear[i].spirit * spirit_weight;
                gear[i].weight += gear[i].crit * crit_weight;
                gear[i].weight += gear[i].intel * intel_weight;

                if (bonus_intact)
                {
                    gear[i].weight += gear[i].bonus_spellpower * spellpower_weight;
                    gear[i].weight += gear[i].bonus_stamina * stamina_weight;
                    gear[i].weight += gear[i].bonus_haste * haste_weight;
                    gear[i].weight += gear[i].bonus_spirit * spirit_weight;
                    gear[i].weight += gear[i].bonus_crit * crit_weight;
                    gear[i].weight += gear[i].bonus_intel * intel_weight;
                    gear[i].hit += gear[i].bonus_hit;
                }

            }

            //separate out all bodyparts
            List<WowItem>[] items = new List<WowItem>[Utils.bodyparts.Length];

            for (int i = 0; i < Utils.bodyparts.Length; i++)
            {
                items[i] = gear.FindAll(delegate(WowItem w) { return w.bodypart == Utils.bodyparts[i]; });
                /*if (i==0)
                    label1.Text = "Start: " + items[0].Count;*/

                //and remove inferior gear for each bodypart...
                items[i].Sort(new WowItemComparer());
                List<WowItem> prunedItems = new List<WowItem>();
                for (int j = 0; j < items[i].Count; j++)
                {
                    //see if we have any items locked to this slot...
                    if (lockedBodyParts[i] > 0)
                    {
                        items[i] = items[i].FindAll(delegate(WowItem w) { return w.wowheadid == lockedBodyParts[i]; });
                    }

                    //do not prune weapon enhancements.
                    if (Utils.bodyparts[i] == "Weapon Enchant")
                        continue;

                    
                    if (Utils.bodyparts[i] == "Meta")
                    {
                        //no pruning
                    } //there the top TWO must be allowed to stay
                    else if (Utils.bodyparts[i] == "Finger" || Utils.bodyparts[i] == "Trinket")
                    {
                        if (Utils.bodyparts[i] == "Trinket" && j > 1 && (items[i][j].hit < items[i][j - 2].hit && items[i][j].hit == items[i][j - 1].hit))
                        {
                            if (items[i].FindAll(delegate(WowItem w) { return w.hit <= items[i][j].hit && w.weight < items[i][j].weight && w.RedGemCount <= items[i][j].RedGemCount && w.BlueGemCount <= items[i][j].BlueGemCount && w.YellowGemCount <= items[i][j].YellowGemCount; }).Count > 0)
                            {
                                items[i].RemoveAll(delegate(WowItem w) { return w.hit <= items[i][j].hit && w.weight < items[i][j].weight && w.RedGemCount <= items[i][j].RedGemCount && w.BlueGemCount <= items[i][j].BlueGemCount && w.YellowGemCount <= items[i][j].YellowGemCount; });
                                j = 0;
                            }
                        }
                        //RINGS NEED A PRUNING MECHANISM!!
                    }
                    else
                    {
                        //Remove all items having <=hit and < weight than this one
                        if (items[i].FindAll(delegate(WowItem w) { return w.hit <= items[i][j].hit && w.weight < items[i][j].weight && w.t7 <= items[i][j].t7 && w.t8 <= items[i][j].t8 && w.t9 <= items[i][j].t9 && w.t10 <= items[i][j].t10 && w.RedGemCount <= items[i][j].RedGemCount && w.BlueGemCount <= items[i][j].BlueGemCount && w.YellowGemCount <= items[i][j].YellowGemCount; }).Count > 0)
                        {
                            items[i].RemoveAll(delegate(WowItem w) { return w.hit <= items[i][j].hit && w.weight < items[i][j].weight && w.t7 <= items[i][j].t7 && w.t8 <= items[i][j].t8 && w.t9 <= items[i][j].t9 && w.t10 <= items[i][j].t10 && w.RedGemCount <= items[i][j].RedGemCount && w.BlueGemCount <= items[i][j].BlueGemCount && w.YellowGemCount <= items[i][j].YellowGemCount; });
                            j = 0;
                        }

                    }
                }
                for (int j = 1; j < items[i].Count; j++)
                { //any duplicates
                    if (items[i][j].name == items[i][j - 1].name && items[i][j].Gems.Count == items[i][j - 1].Gems.Count)
                    {
                        bool duplicate = true;
                        for (int k = 0; k < items[i][j].Gems.Count; k++)
                        {
                            if (items[i][j].Gems[k].name != items[i][j - 1].Gems[k].name)
                                duplicate = false;
                        }
                        if (duplicate)
                        {
                            items[i].RemoveAt(j);
                            j = 0; //reset loop after removing
                        }
                    }

                }
            }

            //INITIAL PRUNING COMPLETE
            //WriteGearToFile(items[0], "prunedgear.xml"); 

            //next step. Begin merging gear into gearsets
            //- start with bodyparts with fewest options to reduce complexity?


            //for each bodypart
            gearsets = new List<Gearset>();
            List<Gearset> gearsetsnew = new List<Gearset>();


            for (int i = 0; i < items.Length; i++)
            {
                //weapons:
                //if we have a 1h weapon, we can use an offhand.
                //if we have a 2h weapon, we can't.
                //best way to do this would be to maintain two
                //separate gearset lists, and merge & prune the two
                //after they've both been run.

                //as such, skip all weapons & offhands
                if (i == Utils.TWOHANDED || i == Utils.OFFHAND || i == Utils.WEAPON || i == Utils.WEAPONENCHANT)
                {
                    continue;
                }
                string newText = "Adding body part " + (i + 1) + " of " + Utils.bodyparts.Length;
                
                //todo restore this
                progress = 100 * i / Utils.bodyparts.Length;
                
                gearsetsnew = CombineGearsetWithItems(gearsets, items[i]);

                gearsets = gearsetsnew;
                gearsets.Sort(new GearsetComparer());

            }

            //now we need two gearsets for the two weapon branches
            List<Gearset> gearsets2 = new List<Gearset>();
            for (int i = 0; i < gearsets.Count; i++)
            {
                gearsets2.Add((Gearset)(gearsets[i].Clone()));
            }

            //todo restore this
            /*if (progressBar1.InvokeRequired)
            {
                progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = 100; }));
            }*/

            //gearset will be for regular weapon+oh
            //gearset2 will be for 2h weapon
            for (int i = 0; i < items.Length; i++)
            {
                //see if we have any items locked to this slot...
                if (lockedBodyParts[i] > 0)
                    items[i] = lockedItems.FindAll(delegate(WowItem w) { return w.wowheadid == lockedBodyParts[i]; });

                if (i == Utils.TWOHANDED || i == Utils.WEAPONENCHANT)
                {
                    if (lockedBodyParts[Utils.WEAPON] == 0 && lockedBodyParts[Utils.OFFHAND] == 0)
                    {
                        gearsets2 = CombineGearsetWithItems(gearsets2, items[i]);
                        gearsets2.Sort(new GearsetComparer());
                    }
                }
                if (i == Utils.OFFHAND || i == Utils.WEAPON || i == Utils.WEAPONENCHANT)
                {
                    if (lockedBodyParts[Utils.TWOHANDED] == 0)
                    {
                        gearsets = CombineGearsetWithItems(gearsets, items[i]);
                        gearsets.Sort(new GearsetComparer());
                    }
                }
            }
            gearsets.AddRange(gearsets2);
            gearsets.Sort(new GearsetComparer());
            gearsets = PruneGearset(gearsets);

            //ok... so we got a gearset. 

            gearsets.Sort(new GearsetHitTargetComparer(targetHit, lostHitWeight, t7_2pc, t7_4pc, t8_2pc, t8_4pc, t9_2pc, t9_4pc, t10_2pc, t10_4pc));

            progress = 100.00;        
        }


        private List<Gearset> CombineGearsetWithItems(List<Gearset> gearset, List<WowItem> items)
        {
            if (items.Count == 0)
                return gearset;

            Utils.LogText("Start CombineGearsetWithItems - [" + items.Count + "] " + items[0].bodypart + " items to combine in");

            //gearset is the original gearset
            //gearsetnew is the new gearset
            List<Gearset> gearsetnew = new List<Gearset>();

            //for each gearset entry
            if (gearset.Count > 0)
            {
                for (int k = 0; k < gearset.Count; k++)
                {
                    //LogText("Gearset iteration " + k + " of " + gearset.Count);
                    //if it's not a staff enchant, or the item is a staff, allow it
                    bool staff;
                    if (gearset[k].partIDs[Utils.TWOHANDED] != 0)
                    {
                        WowItem weapon = gear.Find(delegate(WowItem w) { return w.wowheadid == gearset[k].partIDs[Utils.TWOHANDED]; });
                        if (weapon == null || weapon.subclass != "Staves")
                            staff = false;
                        else
                            staff = true;
                    }
                    else
                        staff = false;

                    //LogText("Before adding items");
                    for (int j = 0; j < items.Count; j++)
                    {
                        //is this items valid? i.e, staff enchant on dagger
                        if (items[j].bodypart != "Weapon Enchant" || !items[j].isStaffEnchant || staff)
                        {
                            Gearset temp = (Gearset)(gearset[k].Clone());
                            temp.AddItem(items[j]);
                            gearsetnew.Add(temp);
                        }
                    }
                    //LogText("After adding items");
                }
            }
            else
            {
                Gearset temp = new Gearset();
                //first item, new gearsets
                //for each WowItem in this bodypart
                for (int j = 0; j < items.Count; j++)
                {
                    temp = new Gearset();
                    temp.AddItem(items[j]);
                    gearsetnew.Add(temp);
                }
            }

            //PRUNE FIRST
            //don't prune weapons or 2h weapons till they've been enchanted
            //as e.g staff can have better enchantments 
            Utils.LogText("Calling prune method");
            if (items[0].bodypart != Utils.bodyparts[Utils.WEAPON] && items[0].bodypart != Utils.bodyparts[Utils.TWOHANDED])
            {
                gearsetnew = PruneGearset(gearsetnew);
            }
            Utils.LogText("End CombineGearsetWithItems");
            return gearsetnew;
        }


        private List<Gearset> PruneGearset(List<Gearset> gearlist)
        {
            Utils.LogText("Start PruneGearset");

            //no point keeping sets with hit > 400, so just discard those too
            //gearlist.RemoveAll(delegate(Gearset gs) { return gs.hit > 400; });

            // - now changed. We're better off setting hit = targethit on these, since we care not
            //one jot if hit is too high, and it helps to prune gear, whilst keeping
            //gearsets with high hit, in case someone decides they want that. It will be necessary to 
            //recalculate the 'correct' hit value for the display though.
            for (int j = 0; j < gearlist.Count; j++)
            {
                if (gearlist[j].hit > targetHit)
                {
                    gearlist[j].hit = targetHit;
                }
            }


            //also must remove any where we have the same ring appearing twice
            gearlist.RemoveAll(delegate(Gearset gs) { return gs.partIDs[Utils.RING1] != 0 && gs.partIDs[Utils.RING1] == gs.partIDs[Utils.RING2]; });
            //ditto trinkets
            gearlist.RemoveAll(delegate(Gearset gs) { return gs.partIDs[Utils.TRINKET1] != 0 && gs.partIDs[Utils.TRINKET1] == gs.partIDs[Utils.TRINKET2]; });

            gearlist.Sort(new GearsetComparer());

            Utils.LogText("prelims done");

            int iterations = 0;
            int resetcount = 0;
            int startcount = gearlist.Count;
            for (int j = 0; j < gearlist.Count; j++)
            {
                iterations++;
                //if 1st gs, or gs has different hit (less) than the one before
                //and more weight!
                //and <= t7 OR weight lower then 4T7+2T7 bonus
                //and <= t8 OR weight lower then 4T8+2T8 bonus
                //and >=meta req for any colour OR weight lower than meta bonus
                int hit = gearlist[j].hit;
                double weight = gearlist[j].weight;
                double weightplusmeta = weight + gearlist[j].metaactiveweight;
                int t7count = gearlist[j].t7count;
                int t8count = gearlist[j].t8count;
                int t9count = gearlist[j].t9count;
                int t10count = gearlist[j].t10count;

                int partid = gearlist[j].partIDs[0];
                int metaredreq = gearlist[j].metaredreq;
                int metabluereq = gearlist[j].metabluereq;
                int metayellowreq = gearlist[j].metayellowreq;
                //if (gearlist.FindAll(delegate(Gearset w) { return w.hit <= hit && w.weight < weight && (w.t7count <= t7count || (w.weight + t7_2pc + t7_4pc) < weight) && (w.t8count <= t8count || (w.weight + t8_2pc + t8_4pc) < weight) && ((w.partIDs[0] == partid && w.metaredreq >= metaredreq && w.metabluereq >= metabluereq && w.metayellowreq >= metayellowreq) || (w.weight + w.metaweight) < weightplusmeta ); }).Count > 0)
                //{
                gearlist.RemoveAll(delegate(Gearset w) { return w.hit <= hit && w.weight < weight && (w.t7count <= t7count || (w.weight + t7_2pc + t7_4pc) < weight) && (w.t8count <= t8count || (w.weight + t8_2pc + t8_4pc) < weight) && (w.t9count <= t9count || (w.weight + t9_2pc + t9_4pc) < weight) && (w.t10count <= t10count || (w.weight + t10_2pc + t10_4pc) < weight) && ((w.partIDs[0] == partid && w.metaredreq >= metaredreq && w.metabluereq >= metabluereq && w.metayellowreq >= metayellowreq) || (w.weight + w.metaweight) < weightplusmeta); });



                //    clean = false;
                //}

                /*if (gearlist.FindAll(delegate(Gearset w) { return w.hit <= gearlist[j].hit && w.weight < gearlist[j].weight && (w.t7count <= gearlist[j].t7count || (w.weight + t7_2pc + t7_4pc) < gearlist[j].weight) && (w.t8count <= gearlist[j].t8count || (w.weight + t8_2pc + t8_4pc) < gearlist[j].weight) && ((w.partIDs[0] == gearlist[j].partIDs[0] && w.metaredreq >= gearlist[j].metaredreq && w.metabluereq >= gearlist[j].metabluereq && w.metayellowreq >= gearlist[j].metayellowreq) || (w.weight + w.metaweight) < (gearlist[j].weight + gearlist[j].metaactiveweight)); }).Count > 0)
                {
                    gearlist.RemoveAll(delegate(Gearset w) { return w.hit <= gearlist[j].hit && w.weight < gearlist[j].weight && (w.t7count <= gearlist[j].t7count || (w.weight + t7_2pc + t7_4pc) < gearlist[j].weight) && (w.t8count <= gearlist[j].t8count || (w.weight + t8_2pc + t8_4pc) < gearlist[j].weight) && ((w.partIDs[0] == gearlist[j].partIDs[0] && w.metaredreq >= gearlist[j].metaredreq && w.metabluereq >= gearlist[j].metabluereq && w.metayellowreq >= gearlist[j].metayellowreq) || (w.weight + w.metaweight) < (gearlist[j].weight + gearlist[j].metaactiveweight)); });
                    clean = false;
                }*/

                //don't need to reset as prune will only ever remove items after the currently examined one.
                /*if (j >= (gearlist.Count-1) && clean==false)
                {
                    j = 0;
                    clean = true;
                    resetcount++;
                }*/

            }

            Utils.LogText("Before removing equivalents [" + gearlist.Count + "]");

            //too many 'equivalent' combinations. Must remove them.
            for (int i = 0; i < gearlist.Count - 1; i++)
            {
                if (gearlist[i].hit == gearlist[i + 1].hit && gearlist[i].weight == gearlist[i + 1].weight)
                {
                    if (gearlist[i].t7count == gearlist[i + 1].t7count && gearlist[i].t8count == gearlist[i + 1].t8count && gearlist[i].t9count == gearlist[i + 1].t9count && gearlist[i].t10count == gearlist[i + 1].t10count)
                    {
                        if (gearlist[i].partIDs[0] == gearlist[i + 1].partIDs[0] && gearlist[i].metaredreq == gearlist[i + 1].metaredreq && gearlist[i].metabluereq == gearlist[i + 1].metabluereq && gearlist[i].metayellowreq == gearlist[i + 1].metayellowreq)
                        {
                            gearlist.RemoveAt(i + 1);
                        }
                    }
                }
            }

            Utils.LogText("After removing equivalents [" + gearlist.Count + "]");

            int endcount = gearlist.Count;
            Utils.LogText("End PruneGearset (" + startcount + "-->" + endcount + " = " + resetcount + "-" + iterations + ")");
            return gearlist;
        }

    }
}
